package org.anachronos.jeep.core.trace;

import java.util.List;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.anachronos.jeep.core.trace.impl.Invocation;
import org.anachronos.jeep.core.trace.impl.MethodInvocation;
import org.anachronos.jeep.core.trace.impl.Metrics;
import org.anachronos.jeep.core.trace.impl.ModelVisitor;
import org.anachronos.jeep.core.trace.impl.SqlStatementExecution;
import org.anachronos.jeep.core.trace.impl.ThreadCallStack;
import org.anachronos.jeep.core.trace.impl.Trace;
import org.anachronos.jeep.core.trace.impl.Transaction;

public class PostProcessModelVisitor implements ModelVisitor {
	private static Logger logger = Logger.getLogger(PostProcessModelVisitor.class.getName());
	private Trace trace;
	private Stack<Invocation> callStack = new Stack<Invocation>();
	
	public void visit(final Trace trace) {
		this.trace = trace;
		List<ThreadCallStack> threadCallStacks = trace.getThreadCallStacks();
		for (ThreadCallStack threadCallStack : threadCallStacks) {
			final Metrics metrics = threadCallStack.getMetrics();
			for (final Invocation invocation : threadCallStack.getChildren()) {
				metrics.add(invocation.getMetrics());
			}
			trace.getMergedCallStack().getMetrics().add(metrics);
		}
		for (final ThreadCallStack threadCallStack : threadCallStacks) {
			threadCallStack.accept(this);
		}
	}

	public void visit(final ThreadCallStack threadCallStack) {
		for (final Invocation invocation : threadCallStack.getChildren()) {
			invocation.accept(this);
		}
	}

	public void visit(final MethodInvocation methodInvocation) {
		final MethodInvocation mergedMethodInvocation = 
			mergeInvocation(methodInvocation);
		mergedMethodInvocation.getSelfMetrics().add(methodInvocation.getSelfMetrics());
		
		if (logger.isLoggable(Level.FINE)) {
			logger.fine("methodName:"  + methodInvocation.getMethodName());
		}
		// TODO: the get method call has the side effect of initializing
		// the association from the method invocation to the profiled method
		methodInvocation.getMethod();
		trace.addToMergeMethodInvocations(methodInvocation);
		
		
		visit((Invocation) methodInvocation);
		
		callStack.pop();
	}

	public void visit(final Transaction transaction) {
		mergeInvocation(transaction);
		
		visit((Invocation) transaction);
		
		callStack.pop();
	}
	
	public void visit(final SqlStatementExecution sqlStatementExecution) {
		final SqlStatementExecution mergedSqlStatementExecution = 
			mergeInvocation(sqlStatementExecution);
		callStack.pop();
		
		final List<Invocation> children = sqlStatementExecution.getChildren();
		final boolean hackForJBossCachedStatement = sqlStatementExecution.getStatement().equals(TraceModelBuilder.MISSING_STATEMENT) 
			&& sqlStatementExecution.getSqlStatementExecutions().size() == 1;
		if (hackForJBossCachedStatement) {
			final SqlStatementExecution realStatement = sqlStatementExecution.getSqlStatementExecutions()
				.iterator().next();
			sqlStatementExecution.setStatement(realStatement.getStatement());
		}
		if (children.size() > 0) {
			logger.config("Removing children of sql statement execution, this might happen when using JBoss!");
			children.clear();
		}
		trace.addToMergedSqlStatementExecutions(mergedSqlStatementExecution);
		
	}

	public void visit(final Invocation invocation) {
		for (final Invocation childInvocation : invocation.getChildren()) {
			childInvocation.accept(this);
		}
	}
	
	@SuppressWarnings("unchecked")
	private <I extends Invocation> I mergeInvocation(final I invocation) {
		Invocation shallowCopy = invocation.shallowCopy();
		I mergedInvocation;
		if (callStack.isEmpty())
			mergedInvocation = (I) trace.getMergedCallStack().lookupOrAdd(shallowCopy);
		else {
			mergedInvocation = (I) callStack.peek().lookupOrAdd(shallowCopy);
		}
		callStack.push(mergedInvocation);
		mergedInvocation.getMetrics().add(invocation.getMetrics());
		return mergedInvocation;
	}
}
